package com.jddup.program.comparator;

import com.baomidou.mybatisplus.annotation.TableName;
import com.jddup.common.enums.JdbcTypeEnums;
import com.jddup.common.pojo.EntityAndDbDifference;
import com.jddup.common.pojo.JddupConfig;
import com.jddup.common.pojo.TableMeta;
import com.jddup.context.CompareContext;
import com.jddup.context.EntityTableMetaContext;
import com.jddup.context.JddConfigContext;
import com.jddup.program.comparator.impl.EntityAndDbComparatorImpl;
import com.jddup.program.comparator.mysql.*;
import com.jddup.program.comparator.postgresql.*;
import com.jddup.program.dtm.DbToTableMetaConvertor;
import com.jddup.program.dtm.mysql.MysqlDbToTableMetaConvertor;
import com.jddup.program.dtm.postgresql.PostgresqlDbToTableMetaConvertor;
import com.jddup.program.etm.EntityToTableMetaConvertor;
import com.jddup.program.etm.EntityToTableMetaConvertorImpl;
import com.jddup.program.etm.mysql.MysqlFieldToTableColumnConvertor;
import com.jddup.program.etm.mysql.MysqlTableColumnColumnDesConvertor;
import com.jddup.program.etm.postgresql.PostgresqlFieldToTableColumnConvertor;
import com.jddup.program.etm.postgresql.PostgresqlTableColumnColumnDesConvertor;
import com.jddup.program.utils.ClasspathPackageScanner;
import com.jddup.program.utils.DatabaseUtils;
import com.jddup.program.utils.MapMergeCollectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.keyvalue.DefaultMapEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.Table;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by ZZB on 2017/12/13.
 */
public class CompatibilityStartup {
    private static final Logger logger = LoggerFactory.getLogger(CompatibilityStartup.class);

    public void reload() {
        JddupConfig config = JddConfigContext.getJddupConfig();
        if (config.getDefaultDataSource() != null) {
            JddConfigContext.getDataSourceMap().put("default", config.getDefaultDataSource());
        }
        if (config.getDefaultEntityScanPackageMap() != null && config.getDefaultEntityScanPackageMap().size() > 0) {
            JddConfigContext.getDataSourceScanPackageMap().put("default", config.getDefaultEntityScanPackageMap());
        }
        if (config.getDataSourceMap() != null) {
            JddConfigContext.getDataSourceMap().putAll(config.getDataSourceMap());
        }
        if (config.getDataSourceScanPackageMap() != null) {
            JddConfigContext.getDataSourceScanPackageMap().putAll(config.getDataSourceScanPackageMap());
        }
        //反向生成 entityScanPackage -> dataSourceAlias 的 Map，并装载到JddConfigContext中
        loadScanPackageDatasourceMap();
        //缓存获取jdbcType
        loadDbTypeMap();
        //将扫描包下的所有class放入到缓存中
        loadScanPackageEntityMap();
        //根据Class生成TableMeta并装载到EntityTableMetaContext
        loadEntityTableMeta();
        //根据数据库表生成TableMeta并装载到EntityTableMetaContext
        loadDbTableMeta();
    }

    public void startCompare() {
        loadDbTableMeta();
        Map<String, TableMeta> entityTableMetaMap = EntityTableMetaContext.getEntityTableMetaMap();
        Map<String, EntityAndDbDifference> tableDifferenceMap;
        Map<String, EntityAndDbDifference> differenceMap;
        List<EntityAndDbDifference> list = entityTableMetaMap.values().stream().map(entityTableMeta -> {
            String tableName = entityTableMeta.getTableName();
            TableMeta dbTableMeta = EntityTableMetaContext.getDbTableMetaMap().get(tableName);
            String dbName = EntityTableMetaContext.getDbDataSourceMap().get(tableName);
            JdbcTypeEnums jdbcTypeEnums = JddConfigContext.getDbTypeMap().get(dbName);
            EntityAndDbComparator entityAndDbComparator = null;

            if (jdbcTypeEnums.equals(JdbcTypeEnums.POSTGRESQL)) {
                entityAndDbComparator = new EntityAndDbComparatorImpl(new PostgresqlAddPkDDLBuilderImpl(), new PostgresqlAddColumnDDLBuilderImpl(),
                        new PostgresqlDropDDLBuilder(), new PostgresqlCreateDDLBuilderImpl(), new PostgresqlDataTypeCompatibility());
            } else if (jdbcTypeEnums.equals(JdbcTypeEnums.MYSQL)) {
                entityAndDbComparator = new EntityAndDbComparatorImpl(new MysqlAddPkDDLBuilderImpl(), new MysqlAddColumnDDLBuilderImpl(),
                        new MysqlDropDDLBuilder(), new MysqlCreateDDLBuilderImpl(), new MysqlDataTypeCompatibility());
            } else if (jdbcTypeEnums.equals(JdbcTypeEnums.SQLSERVER)) {
                entityAndDbComparator = new EntityAndDbComparatorImpl(new MysqlAddPkDDLBuilderImpl(), new MysqlAddColumnDDLBuilderImpl(),
                        new MysqlDropDDLBuilder(), new MysqlCreateDDLBuilderImpl(), new MysqlDataTypeCompatibility());
            } else {
                logger.error("不支持的数据库类型!");
                return null;
            }
            return entityAndDbComparator.comparator(entityTableMeta, dbTableMeta);
        }).filter(Objects::nonNull).collect(Collectors.toList());
        tableDifferenceMap = list.stream().collect(Collectors.toMap(EntityAndDbDifference::getTableName, o -> o));
        differenceMap = list.stream().collect(Collectors.toMap(EntityAndDbDifference::getBeanName, o -> o));
        CompareContext.getBeanNameDifferencesMap().putAll(differenceMap);
        CompareContext.getTableNameDifferencesMap().putAll(tableDifferenceMap);
    }


    /**
     * 反向生成 entityScanPackage -> dataSourceAlias 的 Map，并装载到JddConfigContext中
     */
    private void loadScanPackageDatasourceMap() {
        Map<String, List<String>> entityScanPackageMap = JddConfigContext.getDataSourceScanPackageMap();
        entityScanPackageMap.forEach((datasourceName, strings) -> {
            if (CollectionUtils.isNotEmpty(strings)) {
                strings.forEach(scanPackage -> JddConfigContext.getEntityScanPackageDatasourceMap().put(scanPackage, datasourceName));
            }
        });
    }

    /**
     * 缓存获取jdbcType
     */
    private void loadDbTypeMap() {
        JddConfigContext.getDataSourceMap().forEach((s, dataSource) -> {
            Connection connection = null;
            try {
                connection = dataSource.getConnection();
                String url = connection.getMetaData().getURL();
                JdbcTypeEnums jdbcTypeEnums = DatabaseUtils.getJdbcTypeEnumsByUrl(url);
                JddConfigContext.getDbTypeMap().put(s, jdbcTypeEnums);
                JddConfigContext.getDatasourceNameUrlMap().put(s, url);
            } catch (SQLException e) {
                logger.error("数据库[{}]，连接失败！", s);
            } finally {
                try {
                    if (connection != null) {
                        connection.close();
                    }
                } catch (SQLException e) {
                    logger.error("数据库[{}]，关闭失败！", s, e);
                }
            }
        });
    }

    /**
     * 将扫描包下的所有class放入到缓存中
     */
    private void loadScanPackageEntityMap() {
        List<String> scanPackages = JddConfigContext.getDataSourceScanPackageMap().values().stream()
                .reduce(new ArrayList<>(), (strings, strings2) -> {
                    strings.addAll(strings2);
                    return strings;
                });
        Map<String, List<Class>> map = scanPackages.stream().map(s -> {
            ClasspathPackageScanner scanner = new ClasspathPackageScanner(s);
            List<Class> classList = scanner.getClassList();
            if (classList != null) {
                classList = classList.stream().filter(aClass -> {
                    try {
                        if (Class.forName("javax.persistence.Table") != null) {
                            Table table = (Table) aClass.getAnnotation(Table.class);
                            if (table != null) return true;
                        }
                    } catch (ClassNotFoundException e) {
                        //NOTHING
                    }
                    try {
                        if (Class.forName("com.baomidou.mybatisplus.annotation.TableName") != null) {
                            TableName table = (TableName) aClass.getAnnotation(TableName.class);
                            if (table != null) return true;
                        }
                    } catch (ClassNotFoundException e) {
                        //NOTHING
                    }
                    return false;
                }).collect(Collectors.toList());
                return new DefaultMapEntry<>(s, classList);
            }
            return null;
        }).collect(MapMergeCollectors.toMap(DefaultMapEntry::getKey, DefaultMapEntry::getValue));
        JddConfigContext.getScanPackageEntityMap().putAll(map);
    }

    /**
     * 生成TableMeta并装载到EntityTableMetaContext
     */
    private void loadEntityTableMeta() {
        Map<String, List<Class>> map = JddConfigContext.getScanPackageEntityMap();
        map.forEach((scanPackage, classes) -> {
            String datasourceAlias = JddConfigContext.getEntityScanPackageDatasourceMap().get(scanPackage);
            JdbcTypeEnums jdbcTypeEnums = JddConfigContext.getDbTypeMap().get(datasourceAlias);
            EntityToTableMetaConvertor convertor = null;
            if (jdbcTypeEnums.equals(JdbcTypeEnums.POSTGRESQL)) {
                convertor = new EntityToTableMetaConvertorImpl(new PostgresqlFieldToTableColumnConvertor(),
                        new PostgresqlTableColumnColumnDesConvertor());
            } else if (jdbcTypeEnums.equals(JdbcTypeEnums.MYSQL)) {
                convertor = new EntityToTableMetaConvertorImpl(new MysqlFieldToTableColumnConvertor(),
                        new MysqlTableColumnColumnDesConvertor());
            } else if (jdbcTypeEnums.equals(JdbcTypeEnums.SQLSERVER)) {
                convertor = new EntityToTableMetaConvertorImpl(new MysqlFieldToTableColumnConvertor(),
                        new MysqlTableColumnColumnDesConvertor());
            }
            if (convertor == null) {
                logger.error("数据源[{}]数据库类型暂不支持！", datasourceAlias);
            } else {
                for (Class clazz : classes) {
                    TableMeta tableMeta = convertor.convertFromClass(clazz);
                    EntityTableMetaContext.getEntityDataSourceMap().put(clazz.getName(), datasourceAlias);
                    EntityTableMetaContext.getTableDataSourceMap().put(tableMeta.getTableName(), datasourceAlias);
                    EntityTableMetaContext.getEntityTableMetaMap().put(clazz.getName(), tableMeta);
                }
            }
        });
    }

    /**
     * 根据数据库表生成TableMeta并装载到EntityTableMetaContext
     */
    private void loadDbTableMeta() {
        for (String className : EntityTableMetaContext.getEntityTableMetaMap().keySet()) {
            TableMeta tableMeta = EntityTableMetaContext.getEntityTableMetaMap().get(className);
            String datasourceAlias = EntityTableMetaContext.getEntityDataSourceMap().get(className);
            JdbcTypeEnums jdbcTypeEnums = JddConfigContext.getDbTypeMap().get(datasourceAlias);
            DbToTableMetaConvertor convertor = null;
            if (jdbcTypeEnums.equals(JdbcTypeEnums.POSTGRESQL)) {
                convertor = new PostgresqlDbToTableMetaConvertor();
            } else if (jdbcTypeEnums.equals(JdbcTypeEnums.MYSQL)) {
                convertor = new MysqlDbToTableMetaConvertor();
            }
            if (convertor == null) {
                logger.error("数据源[{}]数据库类型暂不支持！", datasourceAlias);
            } else {
                String tableName = tableMeta.getTableName();
                TableMeta dbTableMeta = null;
                try {
                    dbTableMeta = convertor.convertFromTableName(tableName, datasourceAlias);
                    EntityTableMetaContext.getDbDataSourceMap().put(tableName, datasourceAlias);
                    EntityTableMetaContext.getDbTableMetaMap().put(tableName, dbTableMeta);
                } catch (SQLException e) {
                    logger.error("数据源[{}]，[{}]表转换表结构失败！", datasourceAlias, tableName, e);
                }
            }
        };
    }
}
